﻿// ***********************************************************************
// Assembly         : Tx.OCC
// Author           : tianteng
// Created          : 06-27-2019
//
// Last Modified By : tianteng
// Last Modified On : 06-27-2019
// ***********************************************************************
// <copyright file="OccTriangulationEx.cs" company="北京腾雪科技有限责任公司">
//     Copyright © 北京腾雪科技有限责任公司 2022
// </copyright>
// <summary></summary>
// ***********************************************************************
using System.Collections.Generic;
using Occt;

namespace Tx.OCC
{
    /// <summary>
    /// Class TriangulationData. This class cannot be inherited.
    /// </summary>
    public sealed class TriangulationData
    {
        /// <summary>
        /// The indices
        /// </summary>
        private int[] _Indices;
        /// <summary>
        /// The vertices
        /// </summary>
        private gp_Pnt[] _Vertices;
        /// <summary>
        /// Gets the indices.
        /// </summary>
        /// <value>The indices.</value>
        public int[] Indices => _Indices;

        /// <summary>
        /// Gets the vertices.
        /// </summary>
        /// <value>The vertices.</value>
        public gp_Pnt[] Vertices =>  _Vertices;


        /// <summary>
        /// Gets the triangle count.
        /// </summary>
        /// <value>The triangle count.</value>
        public int TriangleCount => _Indices.Length / 3;

        /// <summary>
        /// Initializes a new instance of the <see cref="TriangulationData"/> class.
        /// </summary>
        /// <param name="indices">The indices.</param>
        /// <param name="vertices">The vertices.</param>
        public TriangulationData(int[] indices, gp_Pnt[] vertices)
        {
            _Indices = indices;
            _Vertices = vertices;
        }
    }

    /// <summary>
    /// Class OccTriangulationEx.
    /// </summary>
    public static class OccTriangulationEx
    {
        /// <summary>
        /// 获得顶点.
        /// </summary>
        /// <param name="triangulation">The triangulation.</param>
        /// <returns>IEnumerable&lt;gp_Pnt&gt;.</returns>
        public static IEnumerable<gp_Pnt> GetNodes(this Poly_Triangulation triangulation)
        {
            for (int i = 1; i <= triangulation.NbNodes; i++)
            {
                yield return triangulation.Node(i);
            }
        }

        /// <summary>
        /// 获得顶点.
        /// </summary>
        /// <param name="triangulation">The triangulation.</param>
        /// <param name="trsf">The TRSF.</param>
        /// <returns>IEnumerable&lt;gp_Pnt&gt;.</returns>
        public static IEnumerable<gp_Pnt> GetNodes(this Poly_Triangulation triangulation, gp_Trsf trsf)
        {
            for (int i = 1; i <= triangulation.NbNodes; i++)
            {
                yield return triangulation.Node(i).Transformed(trsf);
            }
        }

        /// <summary>
        /// Gets the normals.
        /// </summary>
        /// <param name="triangulation">The triangulation.</param>
        /// <returns>IEnumerable&lt;gp_Dir&gt;.</returns>
        public static IEnumerable<gp_Dir> GetNormals(this Poly_Triangulation triangulation)
        {
            for (int i = 1; i <= triangulation.NbNodes; i++)
            {
                yield return triangulation.Normal((i - 1) * 3 + 1);
                yield return triangulation.Normal((i - 1) * 3 + 2);
                yield return triangulation.Normal((i - 1) * 3 + 3);
            }
        }

        /// <summary>
        /// Gets the triangles.
        /// </summary>
        /// <param name="triangulation">The triangulation.</param>
        /// <returns>IEnumerable&lt;Triangle&gt;.</returns>
        public static IEnumerable<Poly_Triangle> GetTriangles(this Poly_Triangulation triangulation)
        {
            for (int i = 1; i <= triangulation.NbTriangles; i++)
            {
                yield return triangulation.Triangle(i);
            }
        }


        /// <summary>
        /// Gets the triangulation.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <returns>TriangulationData.</returns>
        public static TriangulationData GetTriangulation(TopoDS_Shape shape)
        {
            // Ensure that all shapes have a mesh
            if (!BRepTools.Triangulation(shape, Precision.Infinite))
            {
                var aMesher = new BRepMesh_IncrementalMesh(shape, 0.1);
                if (!aMesher.IsDone)
                    return null;
            }

            // Count elements
            int triangleCount = 0;
            int vertexCount = 0;
            var exp = new TopExp_Explorer(shape, TopAbs_ShapeEnum.TopAbs_FACE);
            for (; exp.More; exp.Next())
            {
                var triangulation = BRep_Tool.Triangulation(exp.Current as TopoDS_Face, out var location);
                if (triangulation.IsNull)
                    continue;

                triangleCount += triangulation.NbTriangles;
                vertexCount += triangulation.NbNodes;
            }

            if (triangleCount == 0 || vertexCount == 0)
                return null;

            // Create arrays
            var vertexArray = new gp_Pnt[vertexCount];
            var indexArray = new int[triangleCount * 3];
            int indexOffset = 0;

            int vertexIndex = 0;
            int indexIndex = 0;
            // Copy elements
            exp = new TopExp_Explorer(shape, TopAbs_ShapeEnum.TopAbs_FACE);
            for (; exp.More; exp.Next())
            {
                var triangulation = BRep_Tool.Triangulation(exp.Current as TopoDS_Face, out var location);
                if (triangulation.IsNull)
                    continue;

                // Copy Vertices
                var trsf = location.Transformation;
                for (int nodeIndex = 1; nodeIndex <= triangulation.NbNodes; nodeIndex++)
                {
                    vertexArray[vertexIndex] = triangulation.Node(nodeIndex).Transformed(trsf);
                    vertexIndex++;
                }

                // Copy Indices
                var correctedIndexOffset = indexOffset; // Correct lower bound, this is not 0!
                var triIndices = new int[3];
                for (int triangleIndex = 0; triangleIndex <= triangulation.NbTriangles; triangleIndex++)
                {
                    if (exp.Current.Orientation == TopAbs_Orientation.TopAbs_REVERSED)
                    {
                        triangulation.Triangle(triangleIndex).Get(out triIndices[0], out triIndices[2], out triIndices[1]);
                    }
                    else
                    {
                        triangulation.Triangle(triangleIndex).Get(out triIndices[0], out triIndices[1], out triIndices[2]);
                    }

                    // Copy with face offset
                    indexArray[indexIndex + 0] = triIndices[0] + correctedIndexOffset;
                    indexArray[indexIndex + 1] = triIndices[1] + correctedIndexOffset;
                    indexArray[indexIndex + 2] = triIndices[2] + correctedIndexOffset;
                    indexIndex += 3;
                }

                indexOffset += triangulation.NbNodes;
            }

            // Return
            return new TriangulationData(indexArray, vertexArray);
        }

        

        /// <summary>
        /// Creates the face from triangulation.
        /// </summary>
        /// <param name="triangulationData">The triangulation data.</param>
        /// <returns>TopoDS_Face.</returns>
        public static TopoDS_Face CreateFaceFromTriangulation(TriangulationData triangulationData)
        {
            var vertexCount = triangulationData.Vertices.Length;
            var triangleCount = (triangulationData.Indices != null ? triangulationData.Indices.Length : vertexCount) / 3;
            var triangulation = new Poly_Triangulation(vertexCount, triangleCount, false);

            // Copy vertices
            for (int i = 1; i <= vertexCount; i++) // Note: Nodes-Array starts at 1
            {
                triangulation.SetNode(i, triangulation.Node(i) - triangulationData.Vertices[i - 1]);
            }

            // Copy Indices
            if (triangulationData.Indices != null)
            {
                int indices = 0;
                for (int index = 1; index <= triangleCount; index++, indices += 3) // Note: Triangle-Array starts at 1
                {
                    triangulation.SetTriangle(index, new Poly_Triangle(triangulationData.Indices[indices], triangulationData.Indices[indices + 1] + 1, triangulationData.Indices[indices + 2] + 1)); // Correct lower bound, OCCT needs this to be 1!
                }
            }
            else
            {
                // Create Indices
                int index = 1;
                for (int triangle = 1; triangle <= triangleCount; triangle++, index += 3) // Note: Triangle-Array starts at 1
                {
                    triangulation.SetTriangle(triangle, new Poly_Triangle(index, index + 1, index + 2));
                }
            }

            // Create shape
            var builder = new BRep_Builder();
            builder.MakeFace(out TopoDS_Face face);

            BRepMesh_ShapeTool.AddInFace(face, triangulation);

            return face;
        }
    }
}
